package com.demo.eshop.pdetail.controller;

import com.demo.eshop.pdetail.entity.ProductInventory;
import com.demo.eshop.pdetail.framwork.Response;
import com.demo.eshop.pdetail.request.ProductInventoryCacheReloadRequest;
import com.demo.eshop.pdetail.request.ProductInventoryUpdateRequest;
import com.demo.eshop.pdetail.request.Request;
import com.demo.eshop.pdetail.service.assyn.RequestAsyncProcessService;
import com.demo.eshop.pdetail.service.product.ProductInventoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author: e20160504
 * @date: 2018/10/30
 * @desc
 *
 * 库存场景：
 * 1. 一个更新库存的请求过来，然后此时会先删除redis中的缓存，然后模拟卡顿5s
 * 2. 在卡顿5s内，我们发送一个商品缓存的读请求，因为此时redis中没有请求，就会来请求将数据库中最新库存
 * 3. 此时读请求会路由到同一个内存队列中，阻塞住，不会执行
 *
 */

@RestController
@RequestMapping
public class ProductInventoryController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private RequestAsyncProcessService requestAsyncProcessService;
    @Resource
    private ProductInventoryService productInventoryService;

    @GetMapping("/update")
    public Response updateProductInventory(ProductInventory productInventory) {

        logger.info("=====接受到更新商品库存的请求，商品id = {}, 商品库存 = {} ", productInventory.getProductId(),productInventory.getInventoryCnt());

        Request request = new ProductInventoryUpdateRequest(productInventory, productInventoryService);
        requestAsyncProcessService.process(request);

        return new Response(Response.SUCCESS);
    }

    @GetMapping("/get")
    public ProductInventory getProductInventory(Integer productId) {
        logger.info("=====接受到一个商品的读请求，商品id = {} ", productId);

        Request request = new ProductInventoryCacheReloadRequest(productInventoryService, productId,false);
        requestAsyncProcessService.process(request);

        ProductInventory productInventory;
        // 将请求扔给service异步去处理以后，就需要while(true)一会，在这里hang住，去尝试等待前面有商品库存更新的操作，同时缓存刷新的操作，将最新的数据刷新的缓存
        // 返回的
        long startTime = System.currentTimeMillis();
        logger.info("===== 开始时间 startTime = {}", startTime);
        long waitTime = 0L;
        long endTime;

        try {
            while (true) {

                logger.info("===== 等待时间 waitTime = {}", waitTime);

                if (waitTime > 200) {
                    break;
                }
                //首先尝试从redis读取
                productInventory = productInventoryService.getProductInventoryCache(productId);
                if (productInventory != null) {
                    logger.info("===在20ms内读取到了redis缓存，商品Id = {}，商品库存 = {}",productId,productInventory.getInventoryCnt());
                    return productInventory;
                } else {
                    Thread.sleep(20);
                    endTime = System.currentTimeMillis();
                    waitTime = endTime - startTime;
                }
            }

            // 如果200ms没有从cache中读到数据则尝试直接从数据库中读取数据
            productInventory = productInventoryService.findProductInventory(productId);
            if(productInventory != null){
                // 将缓存刷新一下
                // 这个过程，实际上是一个读操作的过程，但是没有放在队列中串行去处理，还是有数据不一致的问题
                request = new ProductInventoryCacheReloadRequest(productInventoryService, productId,true);
                requestAsyncProcessService.process(request);

                logger.info("===从数据库中查询到商品Id = {}， 库存 = {}",productInventory.getProductId(),productInventory.getInventoryCnt());

                /**
                *代码会运行到这里，只有三种情况：
                 * 1.就是说，上一次也是读请求，数据刷入了redis,但是redis LRU算法给清理掉了，标志位还是false
                 * 所以此时下一个读请求是从缓存中拿不到数据的，在放一个读请求进队列，让数据去刷新一下
                 * 2. 可能在20ms内，就是读请求在队列中一直积压着，没有等待到他执行（实际生产环境中，基本就比较坑）
                 * 所以一直查一次库，然后给队列里塞进去一个刷新缓存的请求
                 * 3.数据库本身就没有，缓存穿透问题，穿透redis到达mysql库
                */
                return productInventory;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ProductInventory(productId, -1);
    }

}
